﻿using System;
using System.IO;
using System.Collections.Generic;

namespace TestingToolkit
{
    // TODO - Should be able to use a Set instead of a Dictionary to organize options
    // This would be a safer implementation
    /// <summary>
    /// Represents a menu with choices that can be activated via command-line.
    /// Provides a means of transforming command-line input to events
    /// </summary>
    public class CommandLineMenu
    {
        /// <summary>
        /// The default string used as menu header
        /// </summary>
        public static readonly string DefaultHeader;

        /// <summary>
        /// The default string used as menu footer
        /// </summary>
        public static readonly string DefaultFooter;

        /// <summary>
        /// Initializes the DefaultHeader and DefaultFooter static constants
        /// </summary>
        static CommandLineMenu()
        {
            DefaultHeader = String.Empty;
            DefaultFooter = "\n";
        }

        /// <summary>
        /// Represents a choice that may be selected from a CommandLineMenu
        /// </summary>
        public class Option
        {
            /// <summary>
            /// Provides a means of sorting menu options
            /// </summary>
            public sealed class Comparer : IComparer<Option>
            {
                /// <summary>
                /// Compares two CommandLineMenu Options
                /// </summary>
                /// <param name="x">The first Option to compare</param>
                /// <param name="y">The second Option to compare</param>
                /// <returns>
                /// Less than zero i.f.f. x is less than y;
                /// Zero i.f.f. x equals y
                /// Greater than zero i.f.f. x is greater than y;
                /// </returns>
                int IComparer<Option>.Compare(Option x, Option y)
                {
                    return ((int)x.ID - (int)y.ID);
                }
            }

            /// <summary>
            /// Provides a means of constructing Options
            /// </summary>
            public sealed class Factory
            {
                private uint current_id;

                public Factory()
                {
                    current_id = 0;
                }

                /// <summary>
                /// Create a new Option
                /// </summary>
                /// <returns>A reference to a newly-constructed Option</returns>
                public Option Create()
                {
                    return new Option(current_id++);
                }

                /// <summary>
                /// Create a new Option with the specified description
                /// </summary>
                /// <param name="description">The description to assign to the Option object which will be created</param>
                /// <returns>A reference to a newly-constructed Option with the specified description</returns>
                public Option Create(string description)
                {
                    return new Option(current_id++, description);
                }

                public Option Create(string description, Action<string> callback)
                {
                    return new Option(current_id++, description,callback);
                }
            }

            /// <summary>
            /// The string which is used to separate the integer ID of an option
            /// from the description of that option during a call to ToString()
            /// </summary>
            public static string Spacer;

            private uint id;
            private string description;
            private Action<string> callback;

            static Option()
            {
                Spacer = " - ";
            }

            private Option(uint _id)
            {
                description = String.Empty;
                id = _id;
            }

            private Option(uint _id, string _description)
            {
                description = _description;
                id = _id;
            }

            private Option(uint _id, string _description, Action<string> _callback)
            {
                description = _description;
                id = _id;
                callback = _callback;
            }

            public uint ID
            {
                get { return id; }
            }

            public Action<string> Callback
            {
                get { return callback; }
                set { callback = value; }
            }

            public string Description
            {
                get { return description; }
                set { description = value; }
            }

            public override string ToString()
            {
                return id.ToString() + Spacer + Description;
            }

            public override int GetHashCode()
            {
                return ID.GetHashCode();
            }

            public override bool Equals(object obj)
            {
                Option o = obj as Option;
                if (o == null)
                    return base.Equals(obj);

                else
                    return o.ID == ID;
            }
        }

        private string footer;
        private string header;
        private IDictionary<uint,Option> options;
        private Option.Factory optionFactory;

        /// <summary>
        /// Construct a new CommandLineMenu
        /// </summary>
        public CommandLineMenu()
        {
            options = new SortedDictionary<uint, Option>();
            optionFactory = new Option.Factory();
            header = DefaultHeader;
            footer = DefaultFooter;
        }

        /// <summary>
        /// DI-Compatible constructor for a CommandLineMenu
        /// </summary>
        /// <param name="dict">An instance of the IDictionary implementation to use. 
        /// Note that the Clear() method will be called on this instance during construction
        /// of the CommandLineMenu</param>
        public CommandLineMenu(IDictionary<uint, Option> dict)
        {
            options = dict;
            options.Clear();
            optionFactory = new Option.Factory();
            header = DefaultHeader;
            footer = DefaultFooter;
        }

        /// <summary>
        /// Gets or sets the footer which is displayed along with this menu
        /// </summary>
        public string Footer
        {
            get { return footer; }
            set { footer = value; }
        }

        /// <summary>
        /// Gets or sets the header which is displayed along with this menu
        /// </summary>
        public string Header
        {
            get { return header; }
            set { header = value; }
        }

        /// <summary>
        /// Gets or sets the option set for this menu
        /// </summary>
        protected IDictionary<uint,Option> OptionDictionary
        {
            get { return options; }
        }

        /// <summary>
        /// Gets the factory used to create new Option instances for this menu
        /// </summary>
        protected Option.Factory OptionFactory
        {
            get { return optionFactory; }
        }

        /// <summary>
        /// Print the menu to an output stream
        /// </summary>
        /// <param name="str">A writer which will be used to print the menu</param>
        public void DisplayMenu(TextWriter str)
        {
            str.WriteLine(Header);

            foreach (Option x in OptionDictionary.Values)
                str.WriteLine(x.ToString());

            str.WriteLine(Footer);
        }

        /// <summary>
        /// Attempt to process a string of input and call the delegate associated with the appropriate option
        /// </summary>
        /// <param name="input">The input string</param>
        /// <returns>true if operation was successful and resulted in the execution of an option; false otherwise</returns>
        public bool ProcessInput(string input)
        {
            uint option_id;
            string[] words = input.Trim().Split(' ');

            if (words.Length < 1
                || !uint.TryParse(words[0], out option_id))
                return false;

            return CallOptionByID(option_id, input);
        }

        /// <summary>
        /// Attempt to obtain and process line of input
        /// </summary>
        /// <param name="reader">The reader used to acquire a line of input</param>
        /// <returns>true if operation was successful and resulted in the execution of an option; false otherwise</returns>
        public bool AcquireAndProcessInput(TextReader reader)
        {
            string line = reader.ReadLine();
            return ProcessInput(line);
        }

        /// <summary>
        /// Add a new option to the menu
        /// </summary>
        /// <param name="optionDescription">A description for the new menu option</param>
        /// <param name="callback">A callback delegate to bind to the option</param>
        public void AddOption(string optionDescription, Action<string> callback)
        {
            Option newOption = optionFactory.Create(optionDescription,callback);
            OptionDictionary.Add(newOption.ID,newOption);
        }

        /// <summary>
        /// Invoke the System.Action associated with the specified option ID
        /// </summary>
        /// <param name="option_id">The ID of the option to invoke</param>
        /// <param name="input">The input string to pass to the option delegate</param>
        /// <returns>true if the operation was successful; false if the specified option was not found</returns>
        public bool CallOptionByID(uint option_id, string input)
        {
            if (OptionDictionary.ContainsKey(option_id))
            {
                OptionDictionary[option_id].Callback.Invoke(input);
                return true;
            }

            return false;
        }

        /// <summary>
        /// Invoke the System.Action associated with the specified option
        /// </summary>
        /// <param name="opt">A reference to the option to invoke</param>
        /// <param name="input">The input string to pass to the option delegate</param>
        /// <returns>true if the operation was successful; false if the specified option was not found</returns>
        public bool CallOptionByReference(Option opt, string input)
        {
            throw new NotImplementedException("CommandLineMenu.CallOptionByReference method is not implemented");
            /*
            if (OptionDictionary.ContainsValue(opt))
            {
                OptionDictionary[opt].Invoke(input);
                return true;
            }

            return false;
            */
        }
    }
}
